from cStringIO import StringIO
import os
from description import Description
#from project import *
import re
from error import BuildError
Project=None
from core.config import Config



SYMBOL="();=[] \"'\\/+-*!?:,%><&|   "
BUILD_IN=["true","false","none","trace","in",">>","return","parseInt","parseFloat","null","this","&&","||","is","as"]



PROTECTOR={
    "backDoubleQuote":'\\"',
    "backSingleQuote":"\\'",
    
    #"object":"{.*}",
    "string1":"'[^']*'",
    "string2":'"[^"]*"'
}


GENERIC={
    "type":"[a-zA-Z0-9_\.\)\(\]\[\<\>]*",
    "all":"[a-zA-Z0-9_\.\)\(\]\[\<\>=   !\-]*",
    "split":"[,=\(\) :\!\[\];+\-/\*\<\>\{\}]",
    
    
}

RULES={
    "package":"package[ \t]+([a-zA-Z0-9_\.]*)",
    "interface":"interface[ 	]+([a-zA-Z0-9_]+)([ 	]+(implement|extends?)[ 	]+([a-zA-Z0-9_]+))?([^{]*)",
    "header_class":"\[JSClass\(([^)]+)\)\]",
    "import":"import[ \t]([a-zA-Z0-9_\.]+)\.([a-zA-Z0-9_]+)",
    "getter":"(private|public|static|protected)?[ 	]*(private|public|protected|override|static?)[ 	]+function[ 	]+get[ 	]+([a-zA-Z0-9_]+)[ 	]*\(([^)]*)\):?("+GENERIC["type"]+")",
    "setter":"(private|public|static|protected)?[ 	]*(private|public|protected|override|static?)[ 	]+function[ 	]+set[ 	]+([a-zA-Z0-9_]+)[ 	]*\(([^)]*)\):?("+GENERIC["type"]+")",
    "method":"(private|public|static|protected)?[ 	]*(private|public|protected|override|static?)[ 	]+function[ 	]+([a-zA-Z0-9_]+)[ 	]*\(([^)]*)\):?("+GENERIC["type"]+")",
    "property":"(private|public|static|protected)?[ 	]*(private|public|protected|static?)[ 	]+(var|const?)[ 	]+([a-zA-Z0-9_]+):("+GENERIC["type"]+")([^;]*)[;]?",
    "class":"class[ 	]+([a-zA-Z0-9_]+)([ 	]+(implement|extends?)[ 	]+([a-zA-Z0-9_]+))?([^{]*)",
    "bracket_open":"^([ 	]*{)[.]*",
    "bracket_close":"^([ 	]*})[.]*"
}

RULES2={
    
    "header_export":"\[JSExport\(([^)]+)\)\]",
    "header_class":"\[JSClass\(([^)]+)\)\]",
    "header_function":"\[JSFunction\(([^)]+)\)\]",
    
    
    "getter":"(private|public|override)?[ 	]+function[ 	]+get[ 	]+([a-zA-Z0-9_]+)\(([^)]*)\):?("+GENERIC["type"]+")",
    "setter":"(private|public|override)?[ 	]+function[ 	]+set[ 	]+([a-zA-Z0-9_]+)\(([^)]*)\):?("+GENERIC["type"]+")",
    
    
    "if":"[ 	]*if[ 	]*\("+GENERIC["all"]+"\)",
    "bracket_open":"[ 	]*{[.]*",
    "bracket_close":"[ 	]*}[.]*",
    #"text":"['|\"][^\n^\r]*['|\"]",
    "comment_line":"//[^\n^\r]*",
    "comment_block_start":"/\*",
    "comment_block_end":"\*/",
    
    "vector":"Vector.<[a-zA-Z0-9_\.]*>"
}




ERROR=False
    
    
    
class ASFile:
    def __init__(self,path,caller=""):
        global Project
        from project import Project
        
        self.class_imported={}
        
        self.mainScript=False
        self._imported=[]
        self.path=path
        self._caller=caller
        self.export=True
        self.blockComment=False
        self.docComment=None
        self.fileDescription=Description()
        self.fileDescription.asFile=self
        self.fileDescription.path=self.path
        self._parents=[self.fileDescription]
        self.fileDescription.name=path
        self._dependency  = []
        self.rescanDesc=[]
        self._objs={}
        self.lineNumber=0
        self.modifedDate=None
        self._parents[0].name="file"
        self._objs["class"]=Description()
        self._objs["class"].objectType="class"
        self._objs["class"].name=self.path[self.path.rfind("/")+1:self.path.rfind(".")]
        self._objs["class"].asFile=self
        self.lastDesc=self.fileDescription
        self._lastDoc={}
        self.protectedID={}
        
        #self.read()
    def getClass(self):
        return self._objs["class"]
        
    def getLines(self):
        pass
    
    def getFileDescriptions(self):
        return self.fileDescription

    
    def getDependency(self):
        return self._dependency
 
    
     
    def addCommunDescription(self,description):
        parent=None
        description.asFile=self
        if description.forceParent:
            self._parents.append(description)
        if len(self._parents):
            parent=self._parents[-1]
            if parent==description:
                parent=self._parents[-2]
            parent.addChild(description)
            if parent.block==False:
                self._parents.pop()
                parent.block=True
        if parent:
            description.level=parent.level+1
        else:
            description.level=len(self._parents)-1
        
    def read(self,text=None):
        if not text:
            try:
                text=open(self.path).read()
            except:
                raise BuildError(self._caller,-1,"file not found:"+self.path)
        text=self.protector(text)
        #text=text.replace("{","\n{")
       
        self.strIO=StringIO(text)
        self.strIO.seek(0)
        cpt=0
        #self.text=text
        #print text
        while 1:
            
            cpt+=1
            if not self.export:
                return
            try:
                line= self.strIO.next()
            except:
                break
            self.lineNumber+=1
            ret = self.readLine(line)
            if ret:
                
                self.addCommunDescription(ret)
                self._lastDoc={}
    
    def lineProtector(self,line):
        pass
    
    def protector(self,text):
        cpt=0
        for name in PROTECTOR:
            s=re.search(PROTECTOR[name],text)
            while s:
                cpt+=1
                #if name=="object":
                #    text=re.sub(PROTECTOR[name]," = $#"+name+str(cpt)+"#",text,1)
                #    v=s.groups()[0]
                #else:
                text=re.sub(PROTECTOR[name],"$#"+name+str(cpt)+"#",text,1)
                v=s.group()
                s=re.search(PROTECTOR[name],text)
                self.protectedID["$#"+name+str(cpt)+"#"]=v
        return text
    
    def parentCorrector(self,text):
        pass
    
    def readLine(self,line):
        #line=line.replace("\t","")
        line=line.replace("\n","")
        line=line.replace("\r","")
        line=self.removeSpace(line)
        line=re.sub(RULES2["vector"], "Array", line)
        #if line.find("{")+1:
        #    self.lineNumber-=1
        if self.docComment:
            self.read_commanddoc(line)
            return None
        elif self.blockComment and line.find("*/")>-1:
            self.blockComment=False
            line=line[line.find("*/")+2:]
        
        elif self.blockComment:
            return None
        
        if not line:
            return
        self.checkErrorPass1(line)
        for name in RULES:
            exp=re.search(RULES[name],line)
            
            if exp is not None:
                return getattr(self,"read_"+name)(line,exp)
        
        if exp is None:
 
            return self.read_line(line)
            
        return None
    
    def rescan(self):
        for desc in self.rescanDesc:
            desc.scanned=True
            subReader=SubReader(desc,self,desc.parent)
            desc.subDescription=subReader.sub
        self.rescanDesc=[]
    
    def checkErrorPass1(self,line):
        error=False
        error=(line.find("..")!=-1 and line.find("..")!=line.find("...")) or line.find(",,")!=-1
        if error:
            raise BuildError(self.path,self.lineNumber,"Syntaxe error line:"+ line+ " \nparents:"+str(self._parents))
        
    def read_line(self,line):

        
        desc=Description()
        desc.objectType="line"
        if line.find("//")>-1:
            line=line[:line.find("//")]
        if line.find("/**")>-1 and not Config.COMPRESSION:
            self.docComment="@main"
            self.read_commanddoc(line)
            return None
        elif line.find("/*")>-1:
            self.blockComment=True
            line=line[:line.find("/*")]    
        desc.line=line
        
        if self._objs.has_key("class"):
            #try:
            subReader=SubReader(desc,self,self._parents[-1])
            #except:
            #    raise BuildError(self.path,self.lineNumber,"Error when reading line:"+ line+ " \nparents:"+str(self._parents))
            if subReader.rescan:
                desc.scaned=True
                self.rescanDesc.append(desc)
            else:
                desc.subDescription=subReader.sub
        
            if desc.forceParent:
                #self._parents.append(desc)
                self.lastDesc=desc
            
        return desc
    
    def read_commanddoc(self,line):
        if not self._lastDoc.has_key("@main"):
            self._lastDoc["@main"]=[]
        if line.find("*/")>-1:
            line=line.replace("*/","")
            self.docComment=None
        line=line.replace("\t","")
        line=line.replace("/**","")
        line=line.replace("*","")
        line=self.removeSpace(line)
        if line=="":
            return

        if line.find("@param")>-1:
            start=line.find("@param")+6
            end=(line.find(" ")+1 or line.find("	")+1)-1
            name = line[start:end]
            self.docComment="@param "+name
            self._lastDoc["@param "+name]=[self.removeSpace(line[end+1:])]
        elif line.find("@return")>-1:
            self.docComment="@return "
            start=line.find("return")+6
            self._lastDoc["@return"]=[self.removeSpace(line[start:])]
        elif line.find("@")>-1:
            self._lastDoc["@main"].append(line)
        elif self.docComment=="@main":
            self._lastDoc["@main"].append(line)
        elif self._lastDoc.has_key(self.docComment):
            self._lastDoc[self.docComment].append(line)
    
    def read_interface(self,line,exp):
        self.export=False
        #Project().removeAsFile(self.path)
        return None
    
    def read_package(self,line,exp):
        self.lastDesc=Description()
        self.lastDesc.name=exp.group(1)
        self.lastDesc.objectType="package"
        self.fileDescription.name = self.lastDesc.name
        self.fileDescription.value = os.path.splitext(os.path.basename(self.path))[0]

        self._objs["package"]=self.lastDesc
        self.lastDesc.line=line
        lineCheckBlock(self.lastDesc)
        return self.lastDesc
    
    
    def read_import(self,line,exp):
        obj=Description()
        obj.objectType="import"
        
        obj.name=exp.group(1)+'.'+exp.group(2)
        #print obj.name
        for pkg in Config.IGNORE_PKG:
            if obj.name.find(pkg)==0:
                return None
 
        path=exp.group(1).replace('.','/')
        path+='/'+exp.group(2)
        asFile=Project().getASFile(path+'.as')
        if not asFile:
            asFile = Project().createASFile(path+'.as',self.path)
        obj.valueType=asFile
        if asFile.export:
            self._imported.append(asFile.getFileDescriptions())
            self._dependency.append(path+'.as')
        self.class_imported[obj.name]=asFile.getFileDescriptions()
        return obj
    
    def findImportFolder(self,name,raiseError=False):
        path=os.path.dirname(self.path)+"/"+name+".as"
        
        
        listdir=os.listdir(os.path.dirname(self.path))
        try:
            listdir.index(name+".as")
            exist=True
        except:
            exist=False
        if exist:
            asFile=Project().getASFile(path)
            if not asFile:
                asFile = Project().createASFile(path)
            try:
                self._imported.index(asFile.getFileDescriptions())
                return
            except:
                pass
            f=asFile.getFileDescriptions()
            if asFile.export:
                self._imported.append(f)
                self._dependency.append(path+'.as')
            self.class_imported[name]=asFile.getFileDescriptions()
            obj=Description()
            obj.objectType="import"
            obj.name=self._objs["package"].name+'.'+name
            
            self._objs["package"].childs.insert(0,obj)
            
            return asFile
        elif raiseError:
            raise BuildError(self.path,self.lineNumber,"Class " + name + " can not be found")
        return None
    
    def read_class(self,line,exp):
        self.lastDesc=Description()
        self.lastDesc.objectType="class"
        self.lastDesc.name=exp.group(1)
        self.lastDesc.valueType=exp.group(4)
        if self._lastDoc.has_key("@main"):
            self.lastDesc.doc=self._lastDoc
        for f in self._imported:
            classChild=f.asFile.getClass()
            if classChild.name==self.lastDesc.valueType:
                self.lastDesc.valueType=classChild
            
        if type(self.lastDesc.valueType)==str:
            f=self.findImportFolder(self.lastDesc.valueType,True).getFileDescriptions()
                
            classChild = f.findChildByObjectType("class")
            if classChild.name == self.lastDesc.valueType:
                self.lastDesc.valueType=classChild
        self._objs["class"]=self.lastDesc
        self.lastDesc.line=line
        lineCheckBlock(self.lastDesc)
        return self.lastDesc
    
    def read_getter(self,line,exp):
        self.lastDesc=self.read_method(line,exp)
        self.lastDesc.objectType="getter"
        
        return self.lastDesc
    
    def read_setter(self,line,exp):
        self.lastDesc=self.read_method(line,exp)
        self.lastDesc.objectType="setter"
        return self.lastDesc
    
    def read_property(self,line,exp):
        
        self.lastDesc=Description()
        if exp.group(1)=="static" or exp.group(2)=="static":
           self.lastDesc.static=True
           self._objs["class"].use+=1
        if exp.group(1) and exp.group(1)!="static":
            self.lastDesc.access=exp.group(1)
        if exp.group(2) and exp.group(2)!="static":
            self.lastDesc.access=exp.group(2)
        self.lastDesc.name=exp.group(4)
        
        
        
        self.lastDesc.objectType="property"
        self.lastDesc.valueType=exp.group(5)
        if exp.group(6):
            self.lastDesc.value=SubReader(self.lastDesc,self,self._parents[-1],False).read(exp.group(6))
        return self.lastDesc
    
    def read_method(self,line,exp):
        self.lastDesc=Description()
        self.lastDesc.name=exp.group(3)
        self.lastDesc.objectType="method"
        self.lastDesc.valueType=exp.group(5)
        self.lastDesc.line=line
        self.lastDesc.subDescription=self.read_arg(exp.group(4),self.lastDesc)
        if self._lastDoc.has_key("@main"):
            self.lastDesc.doc=self._lastDoc
        if exp.group(1)=="static" or exp.group(2)=="static":
           self.lastDesc.static=True
        if exp.group(1) and exp.group(1)!="static" and exp.group(2)!="override":
            self.lastDesc.access=exp.group(1)
        elif exp.group(2)!="static" and exp.group(2)!="override" :
            self.lastDesc.access=exp.group(2)
        if not self._objs["class"].constructor:
            self._objs["class"].constructor=self._objs["class"].name==self.lastDesc.name

        self.lastDesc.constructor=self._objs["class"].name==self.lastDesc.name
       
        if self.lastDesc.constructor:
            self._objs["class"].constructor=self.lastDesc
        if self.lastDesc.name==self._objs["class"].name:
            self._objs["class"].subDescription=self.lastDesc.subDescription
        
        lineCheckBlock(self.lastDesc)
        
        return self.lastDesc
    
    def read_bracket_open(self,line=None,exp=None):
        if len(self._parents)==0 or self._parents[-1]!=self.lastDesc:
            self._parents.append(self.lastDesc)
        self._parents[-1].block=True
        return None
        
    
    def read_bracket_close(self,line,exp):
        try:
            self._parents.pop()
        except:
            
            raise BuildError(self.path,self.lineNumber,"Bracket Error")
        return None
    
    
    def read_header_class(self,line,exp):
        options=exp.group(1).split(",")
        for value in options:
            prop=value.split("=")
            prop[1]=prop[1][1:-1]
            if prop[0]=="export" and prop[1]=="false":
                self.export=False
            if prop[0]=="script" and prop[1]!="false":
                self._output=prop[1]
        return None
    
    def read_arg(self,args,parent):
        ret=[]
        splitArgs=args.split(',')
        for arg in splitArgs:
            split2=arg.split(':')
            if len(split2)>=1:
                obj=Description()
                obj.parent=parent
                obj.parent=self.lastDesc
                if split2[0][0:3]=="...":
                    obj.name=wrapSpace(split2[0][3:])
                    obj.access="list"
                else:
                    obj.name=wrapSpace(split2[0])
                if len(split2)>=2:
                    split3=split2[1].split('=')
                    obj.valueType=split3[0]
                    if len(split3)>=2:
                        obj.access="optional"
                        
                        obj.subDescription=SubReader(obj,self,obj.parent).read(split3[1])
                obj.objectType="arg"
                if self._lastDoc.has_key("@param "+obj.name):
                    obj.doc=self._lastDoc["@param "+obj.name]
                ret.append(obj)
        
        return ret
 
    def removeSpace(self,line):
        while len(line)>0 and (line[0]==" " or line[0]=="    " or line[0]=="\t"):
            line=line[1:]

        return line


class SubReader:
    def __init__(self,desc,asFile,parent=None,autoRead=True):
        self.mainDesc=desc
        self.parent=parent
        self.desc=desc
        self.asFile=asFile
        
        self.rescan=False
        self.isObject=False
        line=desc.line
        self._line=desc.line;
        if autoRead:
            self.sub=self.read(line)
        
    
    def read(self,line):
        
        self.listSub=re.split("("+GENERIC["split"]+")",line)
        retSub=[]
        self.current=-1
        passAnalyse=False
        len_=len(self.listSub)
        stringIndex=0
        
        for i in range(0,len_):
            if self.current>=i:
                continue
            
            
            self.current=i
            sub=self.listSub[i]
            
            if not len(sub):
                continue


            if sub=="{" and line.find("}")>stringIndex:
                self.isObject=True
            elif self.isObject and sub=="}":
                self.isObject=False
            
            if self.isObject and len_>i+2 and self.listSub[i+1]==":":
                retSub+=sub
                continue
            
            
            
            stringIndex+=len(sub)
            
            if sub[0]=="'" or sub[0]=='"':
               passAnalyse=True
            if passAnalyse:
                if sub[-1]=="'" or sub[-1]=='"':
                   passAnalyse=False
                retSub.append(sub)
                continue

            if not sub:
                continue
            ret=sub
            if hasattr(self,"read_"+sub):
                ret = getattr(self,"read_"+sub)(sub)
            else:
                ret = self.findValueType(sub)
            if type(ret)!=str:
                ret.parent=self.mainDesc
            if type(ret)==list:
                retSub+=ret
            else:
                retSub.append(ret)
            
            #if self.rescan:
            #    break
            
        return retSub
        
    def findValueType(self,sub):


        if SYMBOL.find(sub)+1:
            return sub
        if sub.find("super")==0:
            return self.read_super(sub)    
        #elif hasattr(self,sub):
            #sub.find("super")==0:
        #    return getattr(self,"read_"+sub)
            #return self.read_super(sub)
        try:
            BUILD_IN.index(sub)
            return sub
        except:
            pass
        try:
            float(sub)
            return sub
        except:
            pass

        sanalyse=sub
        if sanalyse.find(".")>0:
            sanalyse=sanalyse.split('.')[0]
            
        child=self.parent.findChild(sanalyse)

        if not child:
            child=self.parent.findSubDescription(sanalyse)

        if child and child.objectType=="pointer":
            child=child.valueType
        if not child:
            classDesc=self.parent.findParentByObjectType("class")
            if classDesc:
                child=classDesc.findChild(sanalyse)
                while classDesc.valueType and not child:
                    child=classDesc.valueType.findChild(sanalyse)
                    classDesc=classDesc.valueType
            if child and child.constructor:
                child=child.parent
                
        if not child:
            if self.asFile.getClass().name==sanalyse:
                child=self.asFile.getClass()
            else:
                self.asFile.findImportFolder(sanalyse)
                for ind in self.asFile.class_imported:
                    if ind.find(sanalyse)==len(ind)-len(sanalyse):
                        child=self.asFile.class_imported[ind].findChildByObjectType("class")
                        break  
        
        if not child:
            self.rescan=True       
                    
        if child:
            if(child.name==sub):
                child.use+=1
            desc=Description()
            desc.objectType="pointer"
            desc.parent=self.mainDesc
            desc.name=sub
            desc.valueType=child
            
            return desc
        if sub[0]!=".":
            f=self.asFile.findImportFolder(sub)
        else:
            f=None
        if f:
            classChild=f.getFileDescriptions().findChildByObjectType("class")
            #if classChild and classChild.name==sub:
            return sub        
        
        if self.desc.scanned:
            showWarning("Type not found: "+sub,os.path.abspath(self.asFile.path),str(self.asFile.lineNumber))
        else:
            self.rescan=True
        return sub
     
    def read_switch(self,sub):
        self.mainDesc.forceParent=True
        self.mainDesc.name="switch"
        return sub
    
    def read_if(self,sub):
        self.mainDesc.forceParent=True
        lineCheckBlock(self.mainDesc)
        #if s.count("{")>s.count("}"):
        #    self.mainDesc.block=True
        return sub
    
    def read_for(self,sub):
        self.mainDesc.forceParent=True
        lineCheckBlock(self.mainDesc)
        return sub
    
    def read_else(self,sub):
        self.mainDesc.forceParent=True
        lineCheckBlock(self.mainDesc)
        return sub
    
    def read_while(self,sub):
        self.mainDesc.forceParent=True
        lineCheckBlock(self.mainDesc)
        return sub
    
    def read_try(self,sub):
        self.mainDesc.forceParent=True
        lineCheckBlock(self.mainDesc)
        return sub
    
    
    def read_catch(self,sub):
        self.mainDesc.forceParent=True
        
        lineCheckBlock(self.mainDesc)
        return sub
    
    def read_super(self,sub):
        desc=Description()
        desc.objectType="super"
        self.parent.hasSuper=True
        split=sub.split(".")
        if len(split)<2:
            desc.name=""
        else:
            desc.name=split[1]
        return desc
    
    def read_new(self,sub):
        desc=Description()
        desc.objectType="new"
    
        self.current+=2
        desc.name=self.listSub[self.current]

        
        if self.buildInType(desc.name):
            return desc
        a= self.asFile.class_imported
        b= self.desc.line

        if self.asFile.getClass().name==desc.name:
            desc.valueType=self.asFile.getClass()
        else:
            f=self.asFile.findImportFolder(desc.name)
            for ind in self.asFile.class_imported:
                index=ind.find(desc.name)
                if index>-1 and index==len(ind)-len(desc.name):
                    desc.valueType=self.asFile.class_imported[ind].findChildByObjectType("class");
                    break;
        
        #print sub,self.desc.line,desc,desc.valueType
        if desc.valueType:
            return desc    
        else:
            self.rescan=True
            return "new " + desc.name
        
    def buildInType(self,objectType):
        #objectType=translateType(objectType)
        type_=["Array","Dictionary","Date","XMLHttpRequest","Vector.","Vector"]
       
        try:
            type_.index(objectType);
            return True
        except:
            return False
    
    def read_var(self,sub):
        desc=Description()
        desc.objectType="var"
        desc.parent=self.mainDesc
        self.current+=2
        desc.name=self.listSub[self.current]
        if len(self.listSub)>self.current+1 and self.listSub[self.current+1]==":":
            desc.valueType=self.listSub[self.current+2]
            self.current+=2
        return desc    
            
    def findNoSpace(self,start):
        while self.listSub[start] is not " " and self.listSub[start]:
            start+=1
        return start
            

def showWarning(message,file_,line):
    if Config.SHOW_WARNING:
        print "Warning: "+message +" \n\tFile: "+file_+" \n\tline: "+line
    
def wrapSpace(text):
    
    while len(text) and (text[0]==" " or text[0]=="  "):
        text=text[1:]
    while len(text) and (text[-1]==" " or text[-1]=="  "):
        text=text[:-1]
    return text

def lineCheckBlock(desc,line=None):
    
    if not line:
        line=desc.line
    #print "lineCheckBlock",line
    if line.count("{")>line.count("}"):
        #print "parent"
        desc.forceParent=True
        desc.block=True
        desc.line=rreplace(desc.line,"{","",1)
        line=rreplace(line,"{","",1)
        desc.name=rreplace(desc.name,"{","",1)
    return line
        
        
        
def rreplace(s, old, new, occurrence):
    li = s.rsplit(old, occurrence)
    return new.join(li)